Skip to content

feat(auto-zoom): configurable zoom with edge snapping, input filtering, and adaptive intensity#1667

Open
namearth5005 wants to merge 11 commits intoCapSoftware:mainfrom
namearth5005:feat/auto-zoom-edge-snap
Open

feat(auto-zoom): configurable zoom with edge snapping, input filtering, and adaptive intensity#1667
namearth5005 wants to merge 11 commits intoCapSoftware:mainfrom
namearth5005:feat/auto-zoom-edge-snap

Conversation

@namearth5005
Copy link

@namearth5005 namearth5005 commented Mar 18, 2026

Summary

  • Configurable auto-zoom: Extract all hardcoded zoom constants into AutoZoomConfig struct with user-facing settings (min/max zoom, sensitivity, smoothing, edge snapping)
  • Input filtering: Double-click deduplication, right-click filtering, and dead zone logic to reduce jittery or duplicate zoom segments
  • Adaptive zoom intensity: Zoom amount now scales based on click spatial density — tight clusters zoom more, spread interactions zoom less
  • Edge snapping: Activate edge-aware viewport snapping for auto-zoom segments so the viewport stays within clean screen bounds

Changes

File What
crates/project/src/configuration.rs New AutoZoomConfig struct with 18 configurable fields and serde defaults
apps/desktop/src-tauri/src/general_settings.rs Wire AutoZoomConfig into GeneralSettingsStore
apps/desktop/src-tauri/src/lib.rs Pass config from settings into zoom segment generation IPC command
apps/desktop/src-tauri/src/recording.rs Replace hardcoded constants, add dead zone, double-click dedup, right-click filter, adaptive intensity
apps/desktop/src/routes/.../experimental.tsx Add UI sliders (min/max zoom, sensitivity, smoothing) and edge snap toggle
crates/rendering/src/lib.rs Activate apply_edge_snap_to_focus for auto-zoom segments
crates/rendering/src/zoom_focus_interpolation.rs Remove #[allow(dead_code)] since function is now used

Test plan

  • Verify existing auto-zoom behavior with default AutoZoomConfig matches prior behavior
  • Toggle edge snapping on/off and confirm viewport behavior changes
  • Adjust min/max zoom sliders and verify zoom intensity changes in preview
  • Confirm double-click produces single zoom segment (not two)
  • Confirm right-clicks do not trigger zoom segments
  • Test sensitivity slider (lower values = more movement triggers zoom)
  • Run cargo test for new unit tests (dead zone, dedup, right-click, intensity scaling)

Comment on lines +176 to +193
<SettingSlider
label="Min Zoom"
value={settings.autoZoomConfig?.minZoomAmount ?? 1.2}
onChange={(v) => handleConfigChange("minZoomAmount", v)}
min={1.0}
max={3.0}
step={0.1}
format={(v) => `${v.toFixed(1)}x`}
/>
<SettingSlider
label="Max Zoom"
value={settings.autoZoomConfig?.maxZoomAmount ?? 2.5}
onChange={(v) => handleConfigChange("maxZoomAmount", v)}
min={1.5}
max={4.0}
step={0.1}
format={(v) => `${v.toFixed(1)}x`}
/>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 No validation that Max Zoom ≥ Min Zoom

The two sliders are fully independent: Min Zoom goes up to 3.0× and Max Zoom starts at 1.5×, so a user can set Min Zoom = 3.0 and Max Zoom = 1.5.

When min_zoom_amount > max_zoom_amount, compute_zoom_amount produces inverted results:

  • A tight cluster of clicks (small max_dist) returns max_zoom_amount (1.5×) — i.e. minimal zoom.
  • Spread activity (large max_dist) returns min_zoom_amount (3.0×) — i.e. maximum zoom.

The effect is completely backwards from the labelling. Consider clamping or cross-validating on change:

onChange={(v) => {
    const clamped = Math.min(v, settings.autoZoomConfig?.maxZoomAmount ?? 4.0);
    handleConfigChange("minZoomAmount", clamped);
}}

and symmetrically for the Max Zoom slider:

onChange={(v) => {
    const clamped = Math.max(v, settings.autoZoomConfig?.minZoomAmount ?? 1.0);
    handleConfigChange("maxZoomAmount", clamped);
}}
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/routes/(window-chrome)/settings/experimental.tsx
Line: 176-193

Comment:
**No validation that Max Zoom ≥ Min Zoom**

The two sliders are fully independent: Min Zoom goes up to 3.0× and Max Zoom starts at 1.5×, so a user can set Min Zoom = 3.0 and Max Zoom = 1.5.

When `min_zoom_amount > max_zoom_amount`, `compute_zoom_amount` produces *inverted* results:
- A tight cluster of clicks (small `max_dist`) returns `max_zoom_amount` (1.5×) — i.e. minimal zoom.
- Spread activity (large `max_dist`) returns `min_zoom_amount` (3.0×) — i.e. maximum zoom.

The effect is completely backwards from the labelling. Consider clamping or cross-validating on change:

```tsx
onChange={(v) => {
    const clamped = Math.min(v, settings.autoZoomConfig?.maxZoomAmount ?? 4.0);
    handleConfigChange("minZoomAmount", clamped);
}}
```

and symmetrically for the Max Zoom slider:

```tsx
onChange={(v) => {
    const clamped = Math.max(v, settings.autoZoomConfig?.minZoomAmount ?? 1.0);
    handleConfigChange("maxZoomAmount", clamped);
}}
```

How can I resolve this? If you propose a fix, please make it concise.

@namearth5005 namearth5005 changed the title feat: activate edge snapping for auto-zoom viewport feat(auto-zoom): configurable zoom with edge snapping, input filtering, and adaptive intensity Mar 20, 2026
…-zero guards

- Add AutoZoomConfig::validated() to clamp min/max zoom and guard intensity_spatial_scale
- Replace silent unwrap_or chains with proper error logging via tracing::error
- Guard division by zero in apply_edge_snap_to_focus for zoom_amount < epsilon
- Guard 0/0 NaN in compute_zoom_amount with intensity_spatial_scale.max(EPSILON)
- Deduplicate edge-snap rendering block into closure
- Remove dead auto_zoom_amount variable
- Add await/catch to handleConfigChange for store persistence
- Fix fragile right_click_ignored test (movement events were masking assertion)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant